#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/bootmem.h>
#include <linux/nodemask.h>
#include <linux/mm.h>
#include <linux/bug.h>

#include <asm/page.h>
#include <asm/setup.h>
#include <asm/map.h>

#include <asm-generic/memory_model.h>
#include <asm-generic/sections.h>

#define MAX_NR_ZONES 4

struct meminfo meminfo;

static void find_node_limits(int node, struct meminfo *mi,
	unsigned long *min, unsigned long *max_low, unsigned long *max_high)
{
	int i;

	*min = -1UL;
	*max_low = *max_high = 0;

	for_each_nodebank(i, mi, node) {
		struct membank *bank = &mi->bank[i];
		unsigned long start, end;

		start = bank_pfn_start(bank);
		end = bank_pfn_end(bank);

		// printf("this is %s(): %d   bank->start = %x, bank->size = %x\r\n", __func__, __LINE__, bank->start, bank->size);
		// printf("this is %s(): %d   start = %d, end = %d\r\n", __func__, __LINE__, start, end);

		// printf("this is %s(): %d   *min = %d\r\n", __func__, __LINE__, *min);
		if (*min > start)
			*min = start;
		// printf("this is %s(): %d   *min = %d\r\n", __func__, __LINE__, *min);
		if (*max_high < end)
			*max_high = end;
		if (bank->highmem)
			continue;
		if (*max_low < end)
			*max_low = end;
	}
}

static unsigned int find_bootmap_pfn(int node, struct meminfo *mi, unsigned int bootmap_pages)
{
	unsigned int start_pfn, i, bootmap_pfn;

	start_pfn   = PAGE_ALIGN(__pa(_end)) >> PAGE_SHIFT;
	bootmap_pfn = 0;

	for_each_nodebank(i, mi, node) {
		struct membank *bank = &mi->bank[i];
		unsigned int start, end;

		start = bank_pfn_start(bank);
		end   = bank_pfn_end(bank);

		printf("this is %s(): %d >> _end = %x, start_pfn = %d, start = %d, end = %d\r\n", 
			__func__, __LINE__, _end, start_pfn, start, end);

		if (end < start_pfn)
			continue;

		if (start < start_pfn)
			start = start_pfn;

		if (end <= start)
			continue;

		if (end - start >= bootmap_pages) {
			bootmap_pfn = start;
			break;
		}
	}

	if (bootmap_pfn == 0)
		BUG();

	return bootmap_pfn;
}

static inline void map_memory_bank(struct membank *bank)
{
// #ifdef CONFIG_MMU
	struct map_desc map;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	map.pfn = bank_pfn_start(bank);
	map.virtual = __phys_to_virt(bank_phys_start(bank));
	map.length = bank_phys_size(bank);
	map.type = MT_MEMORY;
	// map.type = MT_MEMORY_NONCACHED;

	create_mapping(&map);
// #endif
}

static void bootmem_init_node(int node, struct meminfo *mi,
	unsigned long start_pfn, unsigned long end_pfn)
{
	unsigned long boot_pfn;
	unsigned int boot_pages;
	pg_data_t *pgdat;
	int i;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	/*
	 * Map the memory banks for this node.
	 */
	for_each_nodebank(i, mi, node) {
		struct membank *bank = &mi->bank[i];

		printf("this is %s(): %d\r\n", __func__, __LINE__);

		if (!bank->highmem)
			map_memory_bank(bank);
	}

	/*
	 * Allocate the bootmem bitmap page.
	 */
	boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);
	boot_pfn = find_bootmap_pfn(node, mi, boot_pages);

	/*
	 * Initialise the bootmem allocator for this node, handing the
	 * memory banks over to bootmem.
	 */
	// node_set_online(node);
	pgdat = NODE_DATA(node);
	printf("this is %s(): %d   start_pfn = %d, end_pfn = %d\r\n", __func__, __LINE__, start_pfn, end_pfn);
	printf("this is %s(): %d   boot_pages = %d, boot_pfn = %d\r\n", __func__, __LINE__, boot_pages, boot_pfn);
	init_bootmem_node(pgdat, boot_pfn, start_pfn, end_pfn);
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	for_each_nodebank(i, mi, node) {
		struct membank *bank = &mi->bank[i];
		if (!bank->highmem)
			free_bootmem_node(pgdat, bank_phys_start(bank), bank_phys_size(bank));
	}
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	/*
	 * Reserve the bootmem bitmap for this node.
	 */
	reserve_bootmem_node(pgdat, boot_pfn << PAGE_SHIFT,
			     boot_pages << PAGE_SHIFT, BOOTMEM_DEFAULT);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
}

static void bootmem_free_node(int node, struct meminfo *mi)
{
	unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
	unsigned long min, max_low, max_high;
	int i;

	find_node_limits(node, mi, &min, &max_low, &max_high);

	/*
	 * initialise the zones within this node.
	 */
	memset(zone_size, 0, sizeof(zone_size));

	/*
	 * The size of this node has already been determined.  If we need
	 * to do anything fancy with the allocation of this memory to the
	 * zones, now is the time to do it.
	 */
	zone_size[0] = max_low - min;
#ifdef CONFIG_HIGHMEM
	zone_size[ZONE_HIGHMEM] = max_high - max_low;
#endif

	/*
	 * For each bank in this node, calculate the size of the holes.
	 *  holes = node_size - sum(bank_sizes_in_node)
	 */
	memcpy(zhole_size, zone_size, sizeof(zhole_size));
	for_each_nodebank(i, mi, node) {
		int idx = 0;
#ifdef CONFIG_HIGHMEM
		if (mi->bank[i].highmem)
			idx = ZONE_HIGHMEM;
#endif
		zhole_size[idx] -= bank_pfn_size(&mi->bank[i]);
	}

	/*
	 * Adjust the sizes according to any special requirements for
	 * this machine type.
	 */
	// arch_adjust_zones(node, zone_size, zhole_size);

	free_area_init_node(node, zone_size, min, zhole_size);
}

static int meminfo_cmp(const void *_a, const void *_b)
{
	const struct membank *a = _a, *b = _b;
	long cmp = bank_pfn_start(a) - bank_pfn_start(b);
	return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
}

void bootmem_init(void)
{
	struct meminfo *mi = &meminfo;
	unsigned long min, max_low, max_high;
	int node, initrd_node;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	sort(&mi->bank, mi->nr_banks, sizeof(mi->bank[0]), meminfo_cmp, NULL);

	max_low = max_high = 0;

	/*
	 * Run through each node initialising the bootmem allocator.
	 */
	for_each_node(node) {
		unsigned long node_low, node_high;

		find_node_limits(node, mi, &min, &node_low, &node_high);

		if (node_low > max_low)
			max_low = node_low;
		if (node_high > max_high)
			max_high = node_high;

		/*
		 * If there is no memory in this node, ignore it.
		 * (We can't have nodes which have no lowmem)
		 */
		if (node_low == 0)
			continue;
		// printf("this is %s(): %d   min = %d, node_low = %d\r\n", __func__, __LINE__, min, node_low);

		bootmem_init_node(node, mi, min, node_low);

		/*
		 * Reserve any special node zero regions.
		 */
		if (node == 0)
			reserve_node_zero(NODE_DATA(node));
	}

	/*
	 * Now free memory in each node - free_area_init_node needs
	 * the sparse mem_map arrays initialized by sparse_init()
	 * for memmap_init_zone(), otherwise all PFNs are invalid.
	 */
	for_each_node(node)
		bootmem_free_node(node, mi);

	high_memory = __va((max_low << PAGE_SHIFT) - 1) + 1;
	printf("this is %s(): %d	high_memory = 0x%x\r\n", __func__, __LINE__, high_memory);

	/*
	 * This doesn't seem to be used by the Linux memory manager any
	 * more, but is used by ll_rw_block.  If we can get rid of it, we
	 * also get rid of some of the stuff above as well.
	 *
	 * Note: max_low_pfn and max_pfn reflect the number of _pages_ in
	 * the system, not the maximum PFN.
	 */
	// max_low_pfn = max_low - PHYS_PFN_OFFSET;
	// max_pfn = max_high - PHYS_PFN_OFFSET;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

}

static inline void
free_memmap(int node, unsigned long start_pfn, unsigned long end_pfn)
{
	struct page *start_pg, *end_pg;
	unsigned long pg, pgend;

	/*
	 * Convert start_pfn/end_pfn to a struct page pointer.
	 */
	start_pg = pfn_to_page(start_pfn - 1) + 1;
	end_pg = pfn_to_page(end_pfn);

	/*
	 * Convert to physical addresses, and
	 * round start upwards and end downwards.
	 */
	pg = PAGE_ALIGN(__pa(start_pg));
	pgend = __pa(end_pg) & PAGE_MASK;

	/*
	 * If there are free pages between these,
	 * free the section of the memmap array.
	 */
	if (pg < pgend)
		free_bootmem_node(NODE_DATA(node), pg, pgend - pg);
}

static void free_unused_memmap_node(int node, struct meminfo *mi)
{
	unsigned long bank_start, prev_bank_end = 0;
	unsigned int i;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	/*
	 * [FIXME] This relies on each bank being in address order.  This
	 * may not be the case, especially if the user has provided the
	 * information on the command line.
	 */
	for_each_nodebank(i, mi, node) {
		struct membank *bank = &mi->bank[i];

		bank_start = bank_pfn_start(bank);
		if (bank_start < prev_bank_end) {
			printf("MEM: unordered memory banks.  "
				"Not freeing memmap.\n");
			break;
		}

		/*
		 * If we had a previous bank, and there is a space
		 * between the current bank and the previous, free it.
		 */
		if (prev_bank_end && prev_bank_end != bank_start)
			free_memmap(node, prev_bank_end, bank_start);

		prev_bank_end = bank_pfn_end(bank);
	}
}

unsigned long num_physpages;
int totalhigh_pages = 0;
static inline unsigned int nr_free_pages(void) { return 0; }

void mem_init(void)
{
	unsigned int codesize, datasize, initsize;
	int i, node;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

// #ifndef CONFIG_DISCONTIGMEM
// 	max_mapnr   = pfn_to_page(max_pfn + PHYS_PFN_OFFSET) - mem_map;
// #endif

	/* this will put all unused low memory onto the freelists */
	for_each_online_node(node) {
	printf("this is %s(): %d\r\n", __func__, __LINE__);
		pg_data_t *pgdat = NODE_DATA(node);

		free_unused_memmap_node(node, &meminfo);

		if (pgdat->node_spanned_pages != 0)
			totalram_pages += free_all_bootmem_node(pgdat);
	}
	printf("this is %s(): %d\r\n", __func__, __LINE__);

// #ifdef CONFIG_SA1111
// 	/* now that our DMA memory is actually so designated, we can free it */
// 	totalram_pages += free_area(PHYS_PFN_OFFSET,
// 				    __phys_to_pfn(__pa(swapper_pg_dir)), NULL);
// #endif

// #ifdef CONFIG_HIGHMEM
// 	/* set highmem page free */
// 	for_each_online_node(node) {
// 		for_each_nodebank (i, &meminfo, node) {
// 			unsigned long start = bank_pfn_start(&meminfo.bank[i]);
// 			unsigned long end = bank_pfn_end(&meminfo.bank[i]);
// 			if (start >= max_low_pfn + PHYS_PFN_OFFSET)
// 				totalhigh_pages += free_area(start, end, NULL);
// 		}
// 	}
// 	totalram_pages += totalhigh_pages;
// #endif

	/*
	 * Since our memory may not be contiguous, calculate the
	 * real number of pages we have in this system
	 */
	printf("Memory:");
	num_physpages = 0;
	for (i = 0; i < meminfo.nr_banks; i++) {
		num_physpages += bank_pfn_size(&meminfo.bank[i]);
		printf(" %ldMB", bank_phys_size(&meminfo.bank[i]) >> 20);
	}
	printf(" = %luMB total\r\n", num_physpages >> (20 - PAGE_SHIFT));

	codesize = _etext - _text;
	datasize = _end - _data;
	// initsize = __init_end - __init_begin;
	initsize = 0;

	printf("Memory: %ldKB available (%dK code, %dK data, %dK init, %ldK highmem)\r\n",
		nr_free_pages() << (PAGE_SHIFT-10), 
		codesize >> 10, datasize >> 10, initsize >> 10,
		(unsigned long) (totalhigh_pages << (PAGE_SHIFT-10)));


	printf("Virtual kernel memory layout:\r\n"
					"     vector  : 0x%08lx - 0x%08lx   (%4ld kB)\r\n"
					"     fixmap  : 0x%08lx - 0x%08lx   (%4ld kB)\r\n"
					"     vmalloc : 0x%08lx - 0x%08lx   (%4ld MB)\r\n"
					"     lowmem  : 0x%08lx - 0x%08lx   (%4ld MB)\r\n"
		#ifdef CONFIG_HIGHMEM
					"     pkmap   : 0x%08lx - 0x%08lx   (%4ld MB)\r\n"
		#endif
		#ifdef CONFIG_MODULES
					"     modules : 0x%08lx - 0x%08lx   (%4ld MB)\r\n"
		#endif
					"      .text : 0x%p" " - 0x%p" "   (%4ld kB)\r\n"
					"      .init : 0x%p" " - 0x%p" "   (%4ld kB)\r\n"
					"      .data : 0x%p" " - 0x%p" "   (%4ld kB)\r\n"
					"      .bss : 0x%p" " - 0x%p" "   (%4ld kB)\r\n",
					// MLK(UL(CONFIG_VECTORS_BASE), UL(CONFIG_VECTORS_BASE) + (PAGE_SIZE)),
		(unsigned long)0xffff0000, (unsigned long)0xffff0000 + PAGE_SIZE,
		(PAGE_SIZE) >> 10,

					// MLK(FIXADDR_START, FIXADDR_END),
		(unsigned long)0, (unsigned long)0,
		((unsigned long)0 - (unsigned long)0) >> 10,

					// MLM(VMALLOC_START, VMALLOC_END),
		(unsigned long)VMALLOC_START, VMALLOC_END,
		(VMALLOC_END - VMALLOC_START) >> 20,

					// MLM(PAGE_OFFSET, (unsigned long)high_memory),
		(unsigned long)PAGE_OFFSET, (unsigned long)high_memory,
		((unsigned long)high_memory - (unsigned long)PAGE_OFFSET) >> 20,

		#ifdef CONFIG_HIGHMEM
					MLM(PKMAP_BASE, (PKMAP_BASE) + (LAST_PKMAP) *
						(PAGE_SIZE)),
		#endif

		#ifdef CONFIG_MODULES
					MLM(MODULES_VADDR, MODULES_END),
		#endif

					// MLK_ROUNDUP(_text, _etext),
		(unsigned long)&_text, (unsigned long)&_etext,
		((unsigned long)&_etext - (unsigned long)&_text) >> 10,
					// MLK_ROUNDUP(__init_begin, __init_end),
		(unsigned long)&__init_begin, (unsigned long)&__init_end,
		((unsigned long)&__init_end - (unsigned long)&__init_begin) >> 10,
					// MLK_ROUNDUP(_sdata, _edata),
		(unsigned long)&_data, (unsigned long)&_end,
		((unsigned long)&_end - (unsigned long)&_data) >> 10,
					// MLK ROUNDUP(__bss_start, __bss_stop))
		(unsigned long)&__bss_start, (unsigned long)&__bss_stop,
		((unsigned long)&__bss_stop - (unsigned long)&__bss_start) >> 10);
}
